#include <linux/module.h>

#include "testing.h"
#include "context.h"
#include "paging_api.h"

#define info(...) printk("info " __VA_ARGS__)

void run_until_result (void);
void handle_addr_cmd (struct test_command *c);
struct test_command * get_current_command (void);
void restore_data (void);
void handle (struct test_command *c);
typedef void (*callback)(struct test_command *);
void do_until_result (struct test_command *c, callback func);

struct tester_data TESTER;


void print_int_info_header(void)
{
  info("%-10s%6s%9s%11s\n",
       "test",
       "int?",
       "int num",
       "err code?");
}



void print_int_info(char *name)
{
  struct interrupt_info info;
  memcpy (&info, &TESTER.interrupt_info, sizeof(TESTER.interrupt_info));
  
  info("%-10s%6s%9hu%11s\n",
       name,
       (info.interrupt ? "yes" : "no"),
       info.int_num,
       (info.has_error_code ? "yes" : "no"));
}


void *idt_page;
u64 *pml4;
void init_contexts(void) {
  save_context (&(TESTER.linux_context));
  
  pml4 = (u64 *) get_zeroed_page (GFP_DMA);
  pml4_map_kernel(pml4);
  
  idt_page = (void *) get_zeroed_page (GFP_DMA);
  init_test_idt64(idt_page);
  
  TESTER.test_context.idtr = make_test_idtr(idt_page);
  
  TESTER.test_context.cr3 = (TESTER.linux_context.cr3 & 0xfff) | virt_to_phys(pml4);
}


char glob;

asm("hmm:\n\t"
    "int $5\n\t"
);

struct test_command commands[10];
char *command_name[10];
struct test_page pages[10];
void *temp_page;
void init_tester(void) {
  int i;
  temp_page = (void *) get_zeroed_page (GFP_KERNEL);
  temp_init(&TESTER.tmp, temp_page);
  TESTER.pages = pages;
  
  for (i=0; i<10; ++i) {
    pages[i].info.offset_mask = OFFSET_MASK_4KB;
    pages[i].info.in_memory = 1;
    pages[i].virt = (u64 *) get_zeroed_page (GFP_KERNEL);
    pages[i].phys = virt_to_phys(pages[i].virt);
  }
    
}

void set_comm(void) {
  TESTER.cmd_ind = 0;
  TESTER.commands = commands;
  
  command_name[0] = "read 1000";
  commands[0].page = 0;
  commands[0].has_result = 1;
  commands[0].cmd = CMD_READ;
  commands[0].vaddr = 10000;
  
  command_name[1] = "run int5";
  commands[1].page = 0;
  commands[1].has_result = 1;
  commands[1].cmd = CMD_RUN;
  asm volatile("movq $hmm, %0" : "=r" (commands[1].vaddr));
  
  command_name[2] = "read int5";
  commands[2].page = 0;
  commands[2].has_result = 1;
  commands[2].cmd = CMD_READ;
  asm volatile("movq $hmm, %0" : "=r" (commands[2].vaddr));
  
  command_name[3] = "write int5";
  commands[3].page = 0;
  commands[3].has_result = 1;
  commands[3].cmd = CMD_WRITE;
  asm volatile("movq $hmm, %0" : "=r" (commands[3].vaddr));
  
  command_name[4] = "read glob";
  commands[4].page = 0;
  commands[4].has_result = 1;
  commands[4].cmd = CMD_READ;
  commands[4].vaddr = &glob;
  
  command_name[5] = "write glob";
  commands[5].page = 0;
  commands[5].has_result = 1;
  commands[5].cmd = CMD_WRITE;
  commands[5].vaddr = &glob;
  
  command_name[6] = "run glob";
  commands[6].page = 0;
  commands[6].has_result = 1;
  commands[6].cmd = CMD_RUN;
  commands[6].vaddr = &glob;
  
  command_name[7] = "end";
  commands[7].page = 0;
  commands[7].has_result = 0;
  commands[7].cmd = CMD_END;
  commands[7].vaddr = 0;
}

int n;
void send_to_host(const void *buf, int count) {
  print_int_info(command_name[n++]);
};


void free_memory(void) {
  int i;
  free_page((u64)pml4);
  free_page((u64)idt_page);
  free_page((u64)temp_page);
  
  for (i=0; i<10; ++i)
    free_page((u64) pages[i].virt);
}


static int my_init(void)
{
  struct test_command * c;
  
  info("begin\n");
  init_contexts();
  init_tester();
  set_comm();
  
  info("contexts initialized\n");
  print_int_info_header();
  
  run_test();
  
  free_memory();
  
  info("glob: %d\n", (int)glob);
  info("finish\n");
  
  return 0;
}

static void my_exit(void) {
    info("exit\n");
}

module_init(my_init);
module_exit(my_exit);
